/**
 * 
 */
package fileServer;



import filePackets.*;
import java.net.*;
import java.io.*;
import java.util.*;


import server.IServerApplication;
import server.Logger;
import server.SAExn_TaskIdAlreadyPresent;
import server.SAExn_UnexpectedTaskId;
import server.SAInitTaskContext;
import server.SAGenTaskContext;
import server.ServerApplication;

import cmm.CMMExn_ClusterErr;
import cmm.CMMExn_TaskErr;
import cmm.ClusterManagementModule;
import cmm.ConnectionInformation;


/**
 * @author peleg
 *
 */
public class FileServerApplication extends ServerApplication implements Runnable, IServerApplication {
	/* ----------- private declerations ------------------------- */
	private ClusterManagementModule myCMM;
	private ConnectionInformation myConnectionInformation;
	private ConnectionInformation proxyConnectionInformation;
	private Integer newTaskId;
	private boolean newTaskContextIsValid;
	private InetAddress LocalAddr;
	private DatagramSocket Socket;
	private Logger LogFile;
	private TreeMap<Integer, TaskServiceThread> ThreadsDB;
	private static Thread ServerThread;
	private boolean killed;
	private static String NetworkConnName;
	/* ------------ constant definitions ------------------------- */ 
	public static final int DGRAM_BUF_LEN = 256; 
	private static final String LOG_FILENAME = "ServerApp.log";
	private static final String MyString = "FileServer";
	private static final String ClusterGroupName = "FileServerCluster";
	
	public Condition TaskIDCondition = new Condition();
	public Lock TaskIDLock = new Lock();
	public void UpdateTask(Integer TaskId, SAInitTaskContext newCtx) throws CMMExn_TaskErr, CMMExn_ClusterErr{
		myCMM.AppTaskContextUpdate(TaskId, newCtx); 
	}
	public void FinishTask(Integer TaskId) throws CMMExn_TaskErr, CMMExn_ClusterErr{
		myCMM.AppTaskFinish(TaskId); 
	}
	
	/* ------------ function implementations --------------------- */
	public void LogEntry(String ModuleName, String Entry){
		LogFile.AddEntry(ModuleName, Entry);		
	}
				
	/* (non-Javadoc)
	 * @see server.IServerApplication#ReceiveNewTaskId(int)
	 */
	public synchronized void ReceiveNewTaskId(Integer TaskId) throws SAExn_UnexpectedTaskId, SAExn_TaskIdAlreadyPresent {
		if (!newTaskContextIsValid){
			SAExn_UnexpectedTaskId excpt = new SAExn_UnexpectedTaskId(TaskId);
			throw excpt; 
		}
		newTaskId = TaskId;
		
		TaskIDLock.getWritersLock();
		newTaskContextIsValid = false;
		TaskIDCondition.condNotifyAll();
		TaskIDLock.releaseWritersLock();
		
		return;
	}

	
	public FileServerApplication(Short ProxyPort, Short ServerPort, String DNSServer, String ProxyIP, boolean Interactive, String DefaultGateway, String NetMask) throws CMMExn_ClusterErr, IOException{
//		 prepeare socket
		LogFile = new Logger();
		LogFile.OpenLog(LOG_FILENAME);
		Socket = new DatagramSocket(ServerPort);
				
		killed = false;
		// obtain local IP address to report to CMM
		LocalAddr = InetAddress.getLocalHost();

		myConnectionInformation = new ConnectionInformation();
		myConnectionInformation.putIP(LocalAddr);
		myConnectionInformation.putPort(ServerPort);
		
		proxyConnectionInformation = new ConnectionInformation();
		proxyConnectionInformation.putIP(InetAddress.getByName(ProxyIP));

		proxyConnectionInformation.putPort(ProxyPort);
		
		/* create the CMM */
		myCMM = new ClusterManagementModule(this, myConnectionInformation, proxyConnectionInformation, ClusterGroupName, DefaultGateway, NetMask, NetworkConnName, DNSServer, Interactive);
		ServerThread = new Thread(this);
		ServerThread.start();
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		FileServerApplication myRef;
		Short ProxyPort = 4096;
		Short ServerPort = 2048;
		boolean Interactive;
		
		if (args.length < 6){
			System.out.println("ERROR: Invalid number of command-line parameters");
			System.out.println("Usage: cmd <DNS IP-addr> <Proxy-IP> <yes/no> \"<Network Connection Name>\" <Default Gateway> <Network Mask> [Proxy-port] [Server-port]");
			System.out.println("yes/no selects wether to do training sessions in interactive mode");	
			return;			
		}
		String DNSServer = new String(args[0]);
		System.out.println("DNS Server is "+DNSServer);
		String ProxyIP = new String(args[1]);
		System.out.println("proxy server IP is "+ProxyIP);
		
		if (args[2].equalsIgnoreCase("yes")){
			Interactive = true;
		} else{
			if (args[2].equalsIgnoreCase("no"))
				Interactive = false;
			else{
				System.out.println("third argument in command line should be either yes or no");
				return;
			}
		}
			
		
		NetworkConnName = args[3];
		System.out.println("Network Connection Name is: " + NetworkConnName);
		
		String DefaultGateway = new String(args[4]);
		System.out.println("Default gateway is " + DefaultGateway);
		String NetMask = new String(args[5]);
		System.out.println("Network mask is " + NetMask);
		
		
		if (args.length > 6){
			ProxyPort = new Short(args[6]);
			System.out.println("proxy port is set from command line to "+ProxyPort);
		}
		System.out.println("proxy port is "+ProxyPort);
		if (args.length > 7){
			ServerPort = new Short(args[7]);			
		}			
		System.out.println("server port is "+ServerPort);
		
		try{
			myRef = new FileServerApplication(ProxyPort, ServerPort, DNSServer, ProxyIP, Interactive, DefaultGateway, NetMask);
		}catch(Exception E){
			E.printStackTrace();
			System.out.println("FileServerApplication c'tor failed");
			return;			
		}
		
		try {
			ServerThread.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("Server Thread was terminated");
			return;
		}
		System.out.println("Server exited");
		return;		
	}
	
	public void run(){
		newTaskId = new Integer(0);
		newTaskContextIsValid = false;
		
		NewSessionPacket IncomingPacket;
			
		ThreadsDB = new TreeMap<Integer, TaskServiceThread>();
		
		System.out.println("Server is ready to receive new client connections");
		filePackets.Receiver myReceiver = new Receiver();
		
		while (!killed){
			
			try {
				IncomingPacket = (NewSessionPacket)myReceiver.ReadObjectFrom(Socket);
			} catch (IOException e1) {
				System.out.println("Error receiving new conncection packet");
				e1.printStackTrace();
				return;
			}
						
			// prepare connection information for the task context
			ConnectionInformation newConn = new ConnectionInformation();
			newConn.putIP(IncomingPacket.getClientData().getIP());
			newConn.putPort(IncomingPacket.getClientData().getPort());
			
			// prepare new task context
			FileTaskContext newTaskContext = new FileTaskContext();
			newTaskContext.setConnInfo(newConn);
			newTaskContext.setBlockNumber(0);
			newTaskContext.setFileNumber(new Integer(IncomingPacket.getRequestedFile()));

			TaskIDLock.getWritersLock();
			newTaskContextIsValid = true;
			TaskIDLock.releaseWritersLock();
						
			try {
				myCMM.AppNewTask(newTaskContext);
			} catch (CMMExn_ClusterErr e) {
				LogFile.AddEntry(MyString, "Error: CMM reports a cluster error");
				e.printStackTrace();
				return;
			}
			// wait for new task ID to arrive	

			TaskIDLock.getReadersLock();
			while(newTaskContextIsValid){
				TaskIDLock.releaseReadersLock();
				TaskIDCondition.condWait();
				TaskIDLock.getReadersLock();
			}
			TaskIDLock.releaseReadersLock();
			
			LogFile.AddEntry(MyString, "received new task ID "+newTaskId);
			StartServicingTask(new Integer(newTaskId), newTaskContext);	
		}
	}

	public void AddLogEntry(String ModuleName, String Entry) {
		LogFile.AddEntry(ModuleName, Entry);
	}

	public void StartServicingTask(Integer TaskId, SAGenTaskContext newTask) {
		System.out.println("Starting to service task ID: "+TaskId);
		TaskServiceThread ClientThread = new TaskServiceThread(this, TaskId, (FileTaskContext)newTask);
		ThreadsDB.put(TaskId, ClientThread);
	}

	public void StopServicingTask(Integer TaskId) {
		System.out.println("stopped serving task ID: "+TaskId);
				ThreadsDB.get(TaskId).kill();
		try {
			ThreadsDB.get(TaskId).getMyThreadHandle().join();
		} catch (InterruptedException e) {}
		ThreadsDB.remove(TaskId);
	}

	public void UnrecoverableClusterError() {
		// kill all Task Service threads
		Set<Map.Entry<Integer, TaskServiceThread>> mySet = ThreadsDB.entrySet();
		Iterator<Map.Entry<Integer, TaskServiceThread>> Iter = mySet.iterator();
		TaskServiceThread threadToKill;
	
		while (Iter.hasNext()){
			Map.Entry<Integer, TaskServiceThread> TMapEntry = Iter.next();
			threadToKill = TMapEntry.getValue();
			threadToKill.kill();			
		}
		// close CMM
		this.myCMM.Close();
		
		// kill main server tread
		this.killed = true;
		return;
	}

	// discards old context and uses delta as a new context, 
	// this means we don't use incremental tasks contexts here
	public SAGenTaskContext ReconstructTaskContext(SAGenTaskContext OldContext, SAGenTaskContext Delta) {	
		return Delta;
	}
	public void WaitForUser(String message) {
		BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
		System.out.println(message);
		System.out.println("please press [Enter] when ready");
		try {
			stdin.readLine();
		} catch (IOException e) {}		
	}

}
